package com.hxzy.system.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.hutool.jwt.JWTUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hxzy.common.annotation.DataScope;
import com.hxzy.common.constant.CacheConstants;
import com.hxzy.common.constant.Constants;
import com.hxzy.common.core.domain.entity.SysUser;
import com.hxzy.common.core.domain.model.LoginBody;
import com.hxzy.common.core.domain.model.LoginUser;
import com.hxzy.common.core.redis.RedisCache;
import com.hxzy.common.exception.ServiceException;
import com.hxzy.common.exception.user.CaptchaException;
import com.hxzy.common.exception.user.CaptchaExpireException;
import com.hxzy.common.exception.user.UserPasswordNotMatchException;
import com.hxzy.common.utils.MessageUtils;
import com.hxzy.common.utils.ServletUtils;
import com.hxzy.common.utils.ip.AddressUtils;
import com.hxzy.common.utils.ip.IpUtils;
import com.hxzy.manager.AsyncManager;
import com.hxzy.manager.factory.AsyncFactory;
import com.hxzy.system.domain.dto.SysUserSearchDTO;
import com.hxzy.system.mapper.SysUserMapper;
import com.hxzy.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static com.hxzy.common.utils.SecurityUtils.encryptPassword;

/**
 * 后台账户业务逻辑
 */
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper,SysUser> implements ISysUserService {


    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // jwt令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期（默认30分钟）
    @Value("${token.expireTime}")
    private int expireTime;

    @Autowired
    private RedisCache redisCache;

    /**
     *  spring security权限自带的对象，一会我们要去配置
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public String login(LoginBody loginBody) {
       //验证码是否过期
        String redisKey= CacheConstants.CAPTCHA_CODE_KEY+loginBody.getUuid();
        Object redisValue=this.redisCache.getCacheObject(redisKey);
        if(Objects.isNull(redisValue)){
            throw new CaptchaExpireException();
        }

        //验证对不对
        if(!redisValue.toString().equalsIgnoreCase(loginBody.getCode())){
            throw new CaptchaException();
        }

        //删除验证码
        this.redisCache.deleteObject(redisKey);

        //登录以后的对象
        Authentication authenticate=null;
        try{
            //走spring security登录流程  看 权限设计   2、程序员如何实现用户名和密码认证呢  图
            UsernamePasswordAuthenticationToken  userToken=new UsernamePasswordAuthenticationToken(loginBody.getUsername(),loginBody.getPassword());

            //执行登录操作    看图  3.2、UserDetailsService接口（自定义连接数据库查询接口）
             authenticate = this.authenticationManager.authenticate(userToken);

        }catch (Exception e ){
            // 密码错误
            if (e instanceof BadCredentialsException)
            {
                // 登录失败记录，定入到sys_logininfor表里面
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginBody.getUsername(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            }
            else
            {
                // 登录失败记录，定入到sys_logininfor表里面
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(loginBody.getUsername(), Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }

        //取得自定义对象
        LoginUser  loginUser= (LoginUser) authenticate.getPrincipal();

        //生成uuid
        String uuid=IdUtil.fastUUID();
        loginUser.setToken(uuid);
        setUserAgent(loginUser);

        //存入redis中
        refreshToken(loginUser);

        //把uuid换成jwt 使用jwt对uuid再一次的加密
        Map<String,Object> map=new HashMap<>();
        map.put("uuid", uuid);
        String jwtToken= JWTUtil.createToken(map,secret.getBytes(StandardCharsets.UTF_8));

        return jwtToken;
    }

    /**
     * spring security登录，根据用户查询用户信息
     * @param userName 用户名
     * @return
     */
    @Override
    public SysUser selectUserByUserName(String userName) {

        return this.baseMapper.selectUserByUserName(userName);
    }

    /**
     * 设置用户代理信息(浏览器，操作系统，IP)
     *
     * @param loginUser 登录信息
     */
    private void setUserAgent(LoginUser loginUser)
    {
        String uaStr= ServletUtils.getRequest().getHeader("User-Agent");
        UserAgent userAgent = UserAgentUtil.parse(uaStr);

        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        // 浏览器是什么
        loginUser.setBrowser(userAgent.getBrowser().getName());
        // 操作系统是什么
        loginUser.setOs(userAgent.getOs().getName());
    }

    /**
     * 把数据写入到redis中
     *
     * @param loginUser 登录信息
     */
    @Override
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * 60*1000L);
        // 根据uuid将loginUser缓存
        String userKey = CacheConstants.LOGIN_TOKEN_KEY+ loginUser.getToken();
        this.redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }
    /**
     * 分页查询
     *
     *
     *
     *
     */
    @Override
    @DataScope(deptAlias = "d", userAlias = "u")
    public IPage<SysUser> selectUserList(SysUserSearchDTO sysUserSearchDTO) {
        IPage<SysUser> page = PageDTO.of(sysUserSearchDTO.getPageNum(), sysUserSearchDTO.getPageSize());
        return this.baseMapper.selectUserList(page, sysUserSearchDTO);
    }

    /**
     * 更改账户状态
     *
     * @param sysUser
     */
    @Override
    public int changeStatus(SysUser sysUser) {
        return this.baseMapper.changeStatus(sysUser);
    }

    /**
     * 重置用户密码
     *
     * @param sysUser
     *
     * @return
     */
    @Override
    public int restPwd(SysUser sysUser) {
        String encryptPassword = encryptPassword(sysUser.getPassword());
        sysUser.setPassword(encryptPassword);
        return this.baseMapper.restPwd(sysUser);
    }

    /**
     * 通过用户ID查询用户
     *
     * @param userId 用户ID
     *
     * @return 用户对象信息
     */
    @Override
    public SysUser selectUserById(Long userId) {
        return this.baseMapper.selectUserById(userId);
    }

    /**
     * 修改用户信息
     *
     * @param sysUser
     * @return
     */
    @Override
    public int updateUserByUserId(SysUser sysUser) {
        return this.baseMapper.updateUserById(sysUser);
    }

    /**
     * 添加用户
     *
     * @param sysUser
     *
     * @return
     */
    @Override
    public int insertSysUser(SysUser sysUser) {
        if (ObjectUtil.isNull(sysUser)) {
            throw new ServiceException("并无用户");
        }
        if (this.hasExistsUserName(sysUser)) {
            throw new ServiceException("该用户名已存在");
        }
        if (this.hasExistsPhone(sysUser)) {
            throw new ServiceException("该电话号码已存在！");
        }
        if (this.hasExistsEmail(sysUser)) {
            throw new ServiceException("该邮箱已存在！");
        }
        String password = sysUser.getPassword();
        sysUser.setPassword(encryptPassword(password));
        return super.baseMapper.insertSysUser(sysUser);
    }

    /**
     * 判断邮箱是否重复
     *
     * @param sysUser
     *
     * @return
     */
    @Override
    public boolean hasExistsEmail(SysUser sysUser) {
        QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
        wrapper.ne("user_id", sysUser.getUserId());
        if (StrUtil.isBlank(sysUser.getEmail())) {
            return false;
        }
        wrapper.eq("email", sysUser.getEmail());
        return super.baseMapper.selectCount(wrapper) > 0;
    }
    /**
     * 判断电话号码是否重复
     *
     * @param sysUser
     *
     * @return
     */
    @Override
    public boolean hasExistsPhone(SysUser sysUser) {
        QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
        wrapper.ne("user_id", sysUser.getUserId());
        if (StrUtil.isBlank(sysUser.getPhonenumber())) {
            return false;
        }
        wrapper.eq("phonenumber", sysUser.getPhonenumber());
        return super.baseMapper.selectCount(wrapper) > 0;
    }

    /**
     * 用户名是否重复
     *
     * @param sysUser
     *
     * @return
     */
    @Override
    public boolean hasExistsUserName(SysUser sysUser) {
        QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
        wrapper.ne("user_id", sysUser.getUserId());
        if (StrUtil.isBlank(sysUser.getUserName())) {
            return false;
        }
        wrapper.eq("user_name", sysUser.getUserName());
        return super.baseMapper.selectCount(wrapper) > 0;
    }
}
